package com.yumeng.framework.auth.shiro.realm;

import com.yumeng.common.auth.CommAuthDetails;
import com.yumeng.common.helper.AssertHelper;
import com.yumeng.common.utils.SpringUtils;
import com.yumeng.framework.auth.bean.AuthExtDataInfo;
import com.yumeng.common.auth.BaseAuthInfo;
import com.yumeng.framework.auth.service.auth.BaseAuthService;
import com.yumeng.framework.auth.shiro.helper.SerializableByteSource;
import com.yumeng.framework.auth.shiro.token.MyUsernamePasswordToken;
import lombok.extern.slf4j.Slf4j;
import org.apache.shiro.authc.*;
import org.apache.shiro.authz.AuthorizationInfo;
import org.apache.shiro.authz.SimpleAuthorizationInfo;
import org.apache.shiro.realm.AuthorizingRealm;
import org.apache.shiro.subject.PrincipalCollection;

/**
 *
 *  用户Realm
 *  （1）AuthenticatingRealm：shiro中的用于进行认证的领域，实现doGetAuthentcationInfo方法实现用户登录时的认证逻辑；
 *  （2）AuthorizingRealm：shiro中用于授权的领域，实现doGetAuthrozitionInfo方法实现用户的授权逻辑，AuthorizingRealm继承了AuthenticatingRealm
 *  （3）AuthenticatingRealm、AuthorizingRealm这两个类都是shiro中提供了一些现成的realm接口
 *  （4）在与spring整合项目中，shiro的SecurityManager会自动调用这两个方法，从而实现认证和授权，可以结合shiro的CacheManager将认证和授权信息保存在缓存中，提高系统的处理效率。
 * @author wxd
 * @date 2021/12/20 14:43
 */
@Slf4j
public class UsernameRealm extends AuthorizingRealm {

    public static final String DEFAULT_BEAN_NAME = "usernameRealm";

    private volatile BaseAuthService authService;
    private String authServiceName = "";

    public UsernameRealm(String authServiceName){
        this.authServiceName = authServiceName;
        setName("usernameRealm");
        AssertHelper.assertNotBlank(this.authServiceName, "authServiceName is blank");
    }

    public BaseAuthService getAuthService() {
        if (this.authService == null){
            synchronized(BaseAuthService.class){
                if (this.authService == null){
                    this.authService = SpringUtils.getBean(this.authServiceName);
                    AssertHelper.assertNotNull(this.authService, "authService is null");
                }
            }
        }
        return this.authService;
    }

    @Override
    public boolean supports(AuthenticationToken token) {
        return token instanceof MyUsernamePasswordToken;
    }

    @Override
    protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principals) {
        log.debug("UsernameRealm::授权");//可能执行多次 权限判断次数多少，就会执行多少次 如，接口的权限为 demo:user:add,root  则会调用两次

/*        Subject subject = SecurityUtils.getSubject();
        AuthInfo authInfo = (AuthInfo)subject.getPrincipal();*///直接调用subject.getPrincipal获取PrimaryPrincipal（即所谓的第一个）

        SimpleAuthorizationInfo authorizationInfo = new SimpleAuthorizationInfo();
        //待实现
/*        AuthInfo authInfo = (AuthInfo)getAvailablePrincipal(principals);
        if (!authInfo.getSuperAdmin()){
            RolePermPkg pkg = getAuthService().getRoleAndPermissions(authInfo.getIdentityId());
            // 角色加入AuthorizationInfo认证对象
            authorizationInfo.setRoles(pkg.getRoles());
            // 权限加入AuthorizationInfo认证对象
            authorizationInfo.setStringPermissions(pkg.getPerms());
        }else{
            authorizationInfo.addRole("admin");
            authorizationInfo.addStringPermission("*:*:*");
        }*/
        return authorizationInfo;
    }

    @Override
    protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken token) throws AuthenticationException {
        log.debug("UsernameRealm::认证");

        //1.判断用户名  token中的用户信息是登录时候传进来的
        MyUsernamePasswordToken upToken = (MyUsernamePasswordToken)token;
        BaseAuthInfo authInfo = getAuthService().findByAuthKey(upToken.getUsername());

        if(authInfo==null){
            //用户名不存在
            throw new UnknownAccountException("用户名或密码错误");
        }

        //获取扩展数据
        AuthExtDataInfo extDataInfo = getAuthService().getAuthExtDataInfo(authInfo);
        if (extDataInfo != null) {
            authInfo.setDataMap(extDataInfo.getDataMap());
            authInfo.setLoginInfo(extDataInfo.getLoginInfo());
        }

        //待完善
        if (!authInfo.isEnabled()){
            //账号已停用
            throw new DisabledAccountException("账号已停用");
        }

        //details
        CommAuthDetails details = new CommAuthDetails();
        details.setAuthClientType(upToken.getAuthClientType());
        authInfo.setDetails(details);

        //TODO 状态判断完善

        String salt = authInfo.getSalt();
        //2.判断密码
        //第二个字段是user.getPassword()，注意这里是指从数据库中获取的password。第三个字段是realm，即当前realm的名称。
        //这块对比逻辑是先对比username，但是username肯定是相等的，所以真正对比的是password。
        //从这里传入的password（这里是从数据库获取的）和token（filter中登录时生成的）中的password做对比，如果相同就允许登录，
        // 不相同就抛出IncorrectCredentialsException异常。
        //如果认证不通过，就不会执行下面的授权方法了
        return new SimpleAuthenticationInfo(authInfo, authInfo.getCredentials(), new SerializableByteSource(salt), getName());//  new SerializableByteSource(salt) ByteSource.Util.bytes(salt)
    }

}
